// Copyright (C) Microsoft Corporation. All rights reserved.

using System;

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using System.Security.Permissions;
using System.Runtime.Serialization;

using System.Net;
using System.Text;
using System.Xml;
using System.Globalization;

/// <summary>
///  Provide's API's to access SharePoint services using sharepoint web services
///  </summary>

[assembly: CLSCompliant(true)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum)]

namespace Microsoft.DDSolutions.SharePoint
{
    /// <summary>
    ///  Base custom exception for SharePoint proxy. All other SharePoint related custom applications shall
    /// inherit from this.
    /// </summary>
    [Serializable]
    public class SharePointApplicationException : Exception
    {
        private string sharePointServerName;
        private string sharePointSiteName = "";

        public SharePointApplicationException()
        {
        }

        /// <summary>
        /// Constructor accepting a single string message
        /// </summary>
        /// <param name="message">Exception Message</param>

        public SharePointApplicationException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Constructor accepting a single string message and the name of the tfs server
        /// </summary>
        /// <param name="message">Exception Message</param>
        /// <param name="sharePointServer">SharePoint Server host Name</param>

        public SharePointApplicationException(string message, string sharePointServer)
            : base(message)
        {
            sharePointServerName = sharePointServer;
        }

        /// <summary>
        /// Constructor accepting a single string message and the name of the tfs server
        /// </summary>
        /// <param name="message">Exception Message</param>
        /// <param name="sharePointServer">SharePoint Server host Name</param>
        public SharePointApplicationException(string message, string sharePointServer, string siteName)
            : base(message)
        {
            sharePointServerName = sharePointServer;
            sharePointSiteName = siteName;
        }

        /// <summary>
        /// Constructor accepting a string message and an inner exception which will be wrapped by this 
        /// custom exception class
        /// </summary>
        /// <param name="message">Exception Message</param>
        /// <param name="inner">Inner Exception</param>
        public SharePointApplicationException(string message,
                 Exception inner)
            : base(message, inner)
        {
        }

        /// <summary>
        ///  Deserialize the sharePoint servername and sharePoint site name
        /// </summary>
        /// <param name="info">SerializedInfo object</param>
        /// <param name="context">StreamingContext</param>
        protected SharePointApplicationException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            sharePointServerName = info.GetString("sharePointServerName");
            sharePointSiteName = info.GetString("sharePointSiteName");
        }

        /// <summary>
        ///  Serialize the sharepoint server name and sharepoint site name 
        /// </summary>
        /// <param name="info">SerializedInfo object</param>
        /// <param name="context">StreamingContext</param>
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            info.AddValue("sharePointServerName", sharePointServerName, typeof(String));
            info.AddValue("sharePointSiteName", sharePointSiteName, typeof(String));
            base.GetObjectData(info, context);
        }

        /// <summary>
        ///  Name of the team foundation server
        /// </summary>
        public string SharePointServer
        {
            get
            {
                return sharePointServerName;
            }
            set
            {
                sharePointServerName = value;
            }
        }

        /// <summary>
        ///  Name of the current team project 
        /// </summary>
        public string Site
        {
            get
            {
                return sharePointSiteName;
            }
            set
            {
                sharePointSiteName = value;
            }
        }
    }

    [Serializable]
    public class InvalidSPServerException : SharePointApplicationException
    {
        public InvalidSPServerException() : base() { }
        public InvalidSPServerException(string msg, string sharePointServer) : base(msg, sharePointServer) { }
        public InvalidSPServerException(string msg) : base(msg) { }
        public InvalidSPServerException(string msg, Exception inner) : base(msg, inner) { }

        /// <summary>
        ///  Serialize the sharepoint server name and sharepoint site name 
        /// </summary>
        /// <param name="info">SerializedInfo object</param>
        /// <param name="context">StreamingContext</param>
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            base.GetObjectData(info, context);
        }

        /// <summary>
        ///  Deserialize the tfs servername and tfs project name
        /// </summary>
        /// <param name="info">SerializedInfo object</param>
        /// <param name="context">StreamingContext</param>
        protected InvalidSPServerException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    };

    [Serializable]
    public class InvalidSPUserException : SharePointApplicationException
    {
        private string SharePointUser;

        public InvalidSPUserException() : base() { }

        public InvalidSPUserException(string msg, string spUser, string sharePointServer, string siteName)
            : base(msg, sharePointServer, siteName)
        {
            SharePointUser = spUser;
        }

        /// <summary>
        ///  Serialize the sharepoint server name and sharepoint site name 
        /// </summary>
        /// <param name="info">SerializedInfo object</param>
        /// <param name="context">StreamingContext</param>
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            info.AddValue("sharePointUserName", SharePointUser, typeof(String));
            base.GetObjectData(info, context);
        }
        /// <summary>
        ///  Deserialize the tfs servername and tfs project name
        /// </summary>
        /// <param name="info">SerializedInfo object</param>
        /// <param name="context">StreamingContext</param>
        protected InvalidSPUserException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        public InvalidSPUserException(string msg, string sharePointServer) : base(msg, sharePointServer) { }
        public InvalidSPUserException(string msg) : base(msg) { }
        public InvalidSPUserException(string msg, Exception inner) : base(msg, inner) { }
    };

    /// <summary>
    ///    This is the proxy class for any given SharePoint site. Using the API's on this the user should 
    /// be able to connect to any given sharepoint site using the public API's of this proxy class.
    /// </summary>
    public class SPServerProxy : IDisposable
    {
        private SharePointSite.UserGroup SharePointServicesUserGroup;
        private string BaseSiteUrl;
        private string BaseAdminSiteUrl;
        bool disposed;
        private string currentSiteName;
        private string _wssVersionNumber = "6.x";

        /// <summary>
        /// Indicates the MicrosoftSharePointTeamServices http header property value returned when getting responses
        /// from the SharePoint site. Defaults to '6.x' if not otherwise defined.
        /// </summary>
        public string WssVersionNumber
        {
            get { return _wssVersionNumber; }
        }

        /// <summary>
        /// Indicates whether we are dealing with server-level permission (IsProjectSite=false) or project-level permission (IsProjectSite=true)
        /// </summary>
        public bool IsProjectSite
        {
            get;
            set;
        }
   

        /// <summary>
        /// Creates the sharepoint proxy
        /// </summary>
        /// <param name="baseSiteUrl">The SharePoint site URL (from the Registration service)</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")]
        public SPServerProxy(string baseSiteUrl, string adminServiceUrl)
        {
            SharePointServicesUserGroup = new SharePointSite.UserGroup();
            SharePointServicesUserGroup.UseDefaultCredentials = true;
            BaseSiteUrl = baseSiteUrl;

            // add a trailing slash to BaseSiteUrl if needed
            if (!string.IsNullOrEmpty(BaseSiteUrl) && !BaseSiteUrl.EndsWith("/",StringComparison.Ordinal))
                BaseSiteUrl = BaseSiteUrl + "/";

            BaseAdminSiteUrl = adminServiceUrl.Substring(0, adminServiceUrl.IndexOf("/_vti_adm/"));
        }        

        /// <summary>
        /// Sets the Sharepoint Site URI. A tests is made to 
        /// determine if a connection can be made, if not an exception
        /// is thrown. 
        /// 
        /// The method is optimized to only perform  this check
        /// when the siteName is changed, because this method is called
        /// frequently.
        /// </summary>
        /// <param name="siteName">The site name of the Sharepoint site.</param>
        /// <exception cref="InvalidSPServerException">Thrown when an error 
        /// occurs while connecting.</exception>
        public void SetUrl(string siteName)
        {
            if (disposed) throw new ObjectDisposedException("SPServerProxy");

            if (null == currentSiteName || !currentSiteName.Equals(siteName))
            {                
                string siteUri;

                if (IsProjectSite) //project level permissions
                {
                  // string siteUri = "https://" + HostServer + "/sites/" + siteName + "/_vti_bin/usergroup.asmx";  
                  siteUri = BaseSiteUrl + siteName;
                    SharePointServicesUserGroup.Url = siteUri + "/_vti_bin/usergroup.asmx";
                }
                else //server level permissions
                {
                  // string siteUri = "https://" + HostServer + ":" + Port + "/_vti_bin/usergroup.asmx";  
                  siteUri = BaseAdminSiteUrl + "/_vti_bin/usergroup.asmx";
                  SharePointServicesUserGroup.Url = siteUri;
                }

                // Make an attempt to connect to the Sharepoint site.
                try
                {
                    WebRequest request = WebRequest.Create(SharePointServicesUserGroup.Url);
                    // Use the default credentials so the NTLM authentication information
                    // is passed to Sharepoint.
                    request.UseDefaultCredentials = true;
                    WebResponse response = request.GetResponse();
                    // Added the below so that the WSS version number gets set as soon as the site url is set
                    DetermineVersionNumber(response);
                    
                }
                catch (WebException e)
                {
                    string msg = string.Format(CultureInfo.CurrentCulture, SharepointResource.SharepointConnectionError, siteUri, e.Message);
                    throw new InvalidSPServerException(msg, e);
                }

                
                currentSiteName = siteName;
            }
        }

        /// <summary>
        /// Sets the _wssVersionNumber based on the value in the WebResponse for the 
        /// MicrosoftSharePointTeamServices property 
        /// </summary>
        /// <param name="response">The WebResponse from a request to the SharePoint site</param>
        private void DetermineVersionNumber(WebResponse response)
        {
                         
            try
            {
                _wssVersionNumber = response.Headers.Get("MicrosoftSharePointTeamServices");
            }
            catch (NotSupportedException )
            {                
                _wssVersionNumber = "6.x";
            }
        }

        /// <summary>
        /// Evaluates the WSSVersionNumber to determine if the SharePoint site is a WSS 3.0 site
        /// </summary>
        /// <returns>true if the SharePointSite is a WSS 3.0 site else returns false</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is")]
        public bool isWss3()
        {
           return _wssVersionNumber.Trim().StartsWith("12");
        }

        /// <summary>
        ///   Verify if the Team Project has a connected SharePoint site.
        /// </summary>
        /// <param name="siteName">Name of the sharepoint site</param>
        /// <returns>True or False depending on wether or not the site exists</returns>
        public bool VerifySiteExistance(string siteName)
        {
            WebResponse response = null;

            try
            {
                Uri site;

                if (IsProjectSite) //project level permissions
                {
                    // Using the settings page to speed up the process of verifying the 
                    // existance of the site (the default page is really slow).
                    site = new Uri(BaseSiteUrl + siteName + "/_layouts/settings.aspx");
                }
                else //server level permissions
                {
                    site = new Uri("http://" + siteName + ":17012");
                }
                
                WebRequest request = HttpWebRequest.Create(site);
                request.Credentials = System.Net.CredentialCache.DefaultCredentials;
                response = request.GetResponse();
            }
            catch (System.Net.WebException e)
            {
                if (e.Status == WebExceptionStatus.ProtocolError)
                {
                    HttpWebResponse r = e.Response as HttpWebResponse;
                    if (r.StatusCode == HttpStatusCode.NotFound)
                    {
                        return false;
                    }
                }
            }
            finally
            {
                if (response != null)
                    response.Close();
            }

            return true;
        }
        

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed == true) return;

            if (disposing)
            {
                SharePointServicesUserGroup.Dispose();
                disposed = true;
            }
        }

        /// <summary>
        ///  This method returns a readonly role collection of roles that the user belongs to for a 
        /// given sharepoint site. Since these shouldn't get modified by the caller externally 
        /// they are made readonly.
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="siteName">SharePoint Site Name</param>
        /// <returns>ReadOnlyCollection of string</returns>
        public ReadOnlyCollection<string> GetRoleCollectionFromUser(string userName, string siteName)
        {
            SetUrl(siteName);
            ReadOnlyCollection<string> spRoles = new ReadOnlyCollection<string>(new Collection<string>()); ;
            XmlNode result;


            if (siteName == null)
                throw new InvalidSPServerException(siteName);

            if (userName == null)
                throw new InvalidSPUserException(userName, siteName);

            /// Actual call to the SharePoint Web Service to get a list of roles the user belongs to. Its returned
            /// as XML data. 
            try
            {
                result = SharePointServicesUserGroup.GetRoleCollectionFromUser(userName);
                spRoles = SPServerProxy.GetAttributesFromXmlNode(result, "//d:Role", "Name");
            }
            catch (System.Web.Services.Protocols.SoapException)
            {

            }

            return spRoles;
        }

        /// <summary>
        ///   Get all the Roles defined in a SharePoint site.
        /// </summary>
        /// <param name="siteName">Name of the SharePoint Site</param>
        /// <returns>ReadOnlyCollection of string</returns>
        public ReadOnlyCollection<string> GetRoles(string siteName)
        {
            SetUrl(siteName);
            ReadOnlyCollection<string> roles;

            // Actual call to the SharePoint Web Service to get a list of roles. Its returned
            /// as XML data.
            try
            {

                XmlNode result = SharePointServicesUserGroup.GetRoleCollectionFromWeb();

                roles = SPServerProxy.GetAttributesFromXmlNode(result, "//d:Role", "Name");
            }
            catch (System.Net.WebException)
            {
                throw new InvalidSPServerException(SharepointResource.SharepointUnauthorizedError + " " + siteName);
            }

            return roles;
        }

        /// <summary>
        ///   Add a user with a given role to share point
        /// </summary>
        /// <param name="userName">Name of the user</param>
        /// <param name="siteName">SharePoint Site</param>
        /// <param name="role">Name of the role that he is assigned</param>
        /// <param name="displayName">Display Name</param>
        /// <param name="email">User's email address</param>
        /// <param name="description">A descripton/comments field about the user</param>
        public void AddUser(string userName, string siteName, string role, string displayName, string email, string description)
        {
            SetUrl(siteName);

            SharePointServicesUserGroup.AddUserToRole(role, displayName, userName, email, description);

        }

        /// <summary>
        ///  Remove the user from a given role assignment on the Share Point site.
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="siteName">SharePoint Site Name</param>
        /// <param name="role">Role Name</param>
        public void RemoveUserFromRole(string userName, string siteName, string role)
        {
            SetUrl(siteName);

            SharePointServicesUserGroup.RemoveUserFromRole(role, userName);
        }

        public void RemoveUser(string userName, string siteName)
        {
            ReadOnlyCollection<string> assignedRoles = GetRoleCollectionFromUser(userName, siteName);

            foreach (string assignedRole in assignedRoles)
            {
                SharePointServicesUserGroup.RemoveUserFromRole(assignedRole, userName);
            }
        }

        /// <summary>
        ///   Check to see if a given role name exists as a valid role at the given sharepoint site.
        /// </summary>
        /// <param name="roleName">User Role Name</param>
        /// <param name="siteName">SharePoint Site Name</param>
        /// <returns>True or False</returns>
        public bool IsRoleExist(string roleName, string siteName)
        {
            ReadOnlyCollection<string> roles = GetRoles(siteName);

            return roles.Contains(roleName);
        }

        /// <summary>
        ///   Given an xmlnode and a filter for nodetag, an attribute name, it will return the list of 
        ///  attribute values.
        /// </summary>
        /// <param name="result">XMLNode</param>
        /// <param name="filterNodeTag">Filter the Nodes based on Tag Name</param>
        /// <param name="attribName">Attribute Name of the Tag</param>
        /// <returns>A ReadOnlyCollection of Attribute value as string</returns>
        private static ReadOnlyCollection<string> GetAttributesFromXmlNode(XmlNode result, string filterNodeTag, string attribName)
        {
            Collection<string> attributes = new Collection<string>();

            if (String.IsNullOrEmpty(filterNodeTag))
                throw new ArgumentNullException("filterNodeTag");

            if (String.IsNullOrEmpty(attribName))
                throw new ArgumentNullException("attribName");

            if (result != null)
            {
                /// Using the helper function RunXPathQuery, query the returned XML data for Role tags
                /// 
                XmlNodeList attributeNodeList = SPServerProxy.RunXPathQuery(result, filterNodeTag);

                /// Iterate through the nodes of Roles and pick the role name from the XML tag attribute for 
                /// Role.
                foreach (XmlNode xRole in attributeNodeList)
                {
                    XmlAttributeCollection Attribs = xRole.Attributes;
                    string attributeValue = Attribs.GetNamedItem(attribName).Value.ToString();
                    attributes.Add(attributeValue);
                }
            }

            return new ReadOnlyCollection<string>(attributes);
        }


        /// <summary>
        ///    Helper function used to query the XML data returned by the web service.
        /// </summary>
        /// <param name="XmlNodeToQuery">XML Node to query</param>
        /// <param name="XPathQuery">Select filter for the query</param>
        /// <returns>XmlNodeList</returns>
        private static XmlNodeList RunXPathQuery(XmlNode XmlNodeToQuery, string XPathQuery)
        {
            // load the complete XML node and all its child nodes into a XML document
            XmlDocument Document = new XmlDocument();
            Document.LoadXml(XmlNodeToQuery.OuterXml);

            // all the possible namespaces used by SharePoint and a randomly choosen prefix
            const string SharePointNamespacePrefix = "sp";
            const string SharePointNamespaceURI = "http://schemas.microsoft.com/sharepoint/soap/";
            const string ListItemsNamespacePrefix = "z";
            const string ListItemsNamespaceURI = "#RowsetSchema";
            const string PictureLibrariesNamespacePrefix = "y";
            const string PictureLibrariesNamespaceURI = "http://schemas.microsoft.com/sharepoint/soap/ois/";
            const string WebPartsNamespacePrefix = "w";
            const string WebPartsNamespaceURI = "http://schemas.microsoft.com/WebPart/v2";
            const string DirectoryNamespacePrefix = "d";
            const string DirectoryNamespaceURI = "http://schemas.microsoft.com/sharepoint/soap/directory/";

            // now associate with the xmlns namespaces (part of all XML nodes returned
            // from SharePoint) a namespace prefix which we can then use in the queries
            XmlNamespaceManager NamespaceMngr = new XmlNamespaceManager(Document.NameTable);
            NamespaceMngr.AddNamespace(SharePointNamespacePrefix, SharePointNamespaceURI);
            NamespaceMngr.AddNamespace(ListItemsNamespacePrefix, ListItemsNamespaceURI);
            NamespaceMngr.AddNamespace(PictureLibrariesNamespacePrefix, PictureLibrariesNamespaceURI);
            NamespaceMngr.AddNamespace(WebPartsNamespacePrefix, WebPartsNamespaceURI);
            NamespaceMngr.AddNamespace(DirectoryNamespacePrefix, DirectoryNamespaceURI);

            // run the XPath query and return the result nodes
            return Document.SelectNodes(XPathQuery, NamespaceMngr);
        }


    }
}
